#include <stdio.h>
//1️⃣字符指针变量  P91
//①字符型
int main()
{
    char a='w';
    char *pa=&a;//pa是一个字符指针变量，存放字符变量a的地址
    printf("%c\n",a); //输出：w
    printf("%c\n",*pa);//输出：w
    *pa='o';
    printf("%c\n",a);//输出：o
    return 0;
}
//②字符串型
int main()
{
    char arr[]="abcdef";//用常量字符串"abcdef"去初始化字符数组
    printf("%s\n",arr);//输出：abcdef
    char *pa=arr;//pa是一个字符指针变量，存放字符数组的首元素地址
    *pa='w';
    printf("%s\n",arr);//输出：wbcdef
/*  char *p="abcdef";  //p是一个字符指针变量，存放字符串常量"abcdef"的首元素地址
    printf("%s\n",p);//输出：abcdef
    *p='w';//这样会显示运行错误，因为字符指针p存放的是常量字符串的首字符地址，指向的常量字符串中的内容不能被修改
    printf("%s\n",p);
*/
    const char *p="abcdef";//所以我们加上const，要修改常量字符串时在编译阶段就不通过
    printf("%s\n",p);//输出：abcdef
    return 0;
}
//下面是《剑指offer》中的例题：
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    if(str1 ==str2) //str1和str2都表示字符数组首元素地址，虽然数组内容一样，但在内存中是开辟了两个不同的空间去存放这两个内容相同的数组。
    printf("str1 and str2 are same\n");
    else
    printf("str1 and str2 are not same\n");//所以两个字符数组首元素地址不同
    if(str3 ==str4) //str3和str4都是存放常量字符串"hello bit."的首元素地址，而常量字符串存放在常量区（只读数据段）
    printf("str3 and str4 are same\n");//为了节省内存空间，多个相同的常量字符串在内存中只会存储一份，所以首元素地址是相同的
    else
    printf("str3 and str4 are not same\n");
    return 0;
}
//输出：
//str1 and str2 are not same
//str3 and str4 are same
//2️⃣数组指针变量
//指向数组的指针变量，存放数组的地址
int main()
{
    int arr[5]={1,2,3,4,5};
    int (*pa)[5]=&arr;//数组指针变量的初始化
    //pa是数组指针变量，其指针类型就是 int (*)[5]
    int i;
    for(i=0;i<5;i++)
    {
        //printf("%d ",(*pa)[i]);//输出：1 2 3 4 5 
        printf("%d ",*((*pa)+i));//输出：1 2 3 4 5 
        //pa相当于&arr，那*pa就是 *(&arr)，那就是arr，所以*((*pa)+i)可以看作*(arr+i)
    }
    return 0;
}
//但其实我们一般不会这么去使用数组指针变量，一般是在二维数组传参时使用
//二维数组传参的本质
//void test(int arr[3][4],int r,int c)  第一种写法
//void test(int arr[][4],int r,int c)   第二种写法
void test(int (*pa)[4],int r,int c)  //第三种写法
{
    int i,j;
    for(i=0;i<r;i++)
    {
        for(j=0;j<c;j++)
        {
            //printf("%d ",pa[i][j]);
            printf("%d ",*(*(pa+i)+j));
            //pa相当于&arr[0]，pa+1相当于&arr[1]，*(pa+0)就是*(&arr[0])，就是arr[0]，那*(pa+0)+j就是arr[0]+j
            //*(*(pa+0)+j)就是*(arr[0]+j),即arr[0][j]
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][4]={1,2,3,4, 3,4,5,6, 5,6,7,8};
    test(arr,3,4);
    return 0;
}
//P72 二维数组可看作一维数组的数组，每一行就是一个一维数组，并且二维数组的首元素地址就是第一行的地址
//一维数组传参本质是把一维数组的首元素地址传过去，二维数组同理，所以二维数组传参的本质就是把第一行的地址传过去
//把第一行看作一维数组，那就是把一维数组的地址传过去，所以要用数组指针变量来接收
//在我们的例子中这个数组指针类型就是 int(*)[4]，而我们可以任取一个变量名，在这里我用pa
//输出：
//1 2 3 4 
//3 4 5 6
//5 6 7 8
//数组指针与数组首元素指针对比
int main()
{
    int arr[5]={1,2,3,4,5};
    int (*pa)[5]=&arr;//pa是数组指针
    int *p=arr;       //p是数组首元素指针
    printf("%p\n",pa);  //000000000061FE10
    printf("%p\n",pa+1);//000000000061FE24
    printf("%p\n",p);   //000000000061FE10
    printf("%p\n",p+1); //000000000061FE14
    return 0;
}
//3️⃣函数指针变量
//指向函数的指针变量，存放函数的地址
//①函数的地址
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    printf("%p\n",&Add);  //0000000000401550
    printf("%p\n",Add);   //0000000000401550
    printf("%p\n",&Add+1);//0000000000401551
    printf("%p\n",Add+1); //0000000000401551
    return 0;
}
//②函数指针变量的使用
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    int r=Add(10,20);
    printf("%d\n",r); //30

    int (*p)(int,int)=&Add; 
    printf("%d\n",(*p)(10,10)); //20
    //p等价&Add，*p就是*(&Add)，也就是*p相当于Add
    printf("%d\n",p(10,20)); //30

    int (*pa)(int,int)=Add;
    printf("%d\n",pa(20,20)); //40
    //pa相当于Add
    printf("%d\n",(*pa)(10,20)); //30
    return 0;
}
//③有趣代码《C陷阱和缺陷》
(*(void (*)())0)();
//void (*)()是函数指针类型
//(void (*)())0 表示把整型0强制类型转化成函数指针类型，函数指针存放函数的地址，所以0变成0地址了，0地址处有一个无参数无返回值的函数
//(*(void (*)())0)()就是调用0地址处的函数，因为无参数所以没有传参，可以类比前面的(*pa)(10,20)
void (*signal(int,void(*)(int)))(int);
//signal()可以看出signal是一个函数名——函数声明，函数定义，函数调用
//signal(int , void(*)(int))从括号中内容可看出这应该是函数声明，signal函数有两个参数，一个是int类型，一个是void(*)(int)函数指针类型（参数是int类型，无返回值的函数）
//void (*)(int); 把signal(int , void(*)(int))删除可看出signal函数返回类型是void(*)(int)
//可以理解为  void(*)(int) signal(int,void(*)(int));
//void (*signal(int,void(*)(int)))(int);是一个函数声明，声明一个叫signal的函数，两个参数分别是int类型和void(*)(int)函数指针类型，返回类型也是void(*)(int)函数指针类型
//这样写很复杂难懂，那是否可以简化？
//④typedef 类型重命名 P32
typedef int* prt;
typedef int (*parr)[5];//重命名int(*)[5]这种数组指针类型为parr
typedef int(*padd)(int,int);//重命名int(*)(int,int)这种函数指针类型为padd
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    int *p1,p2;//此时*只跟p1结合，p1是int*类型，p2是int类型，见8.1.c 26行
    prt p3,p4;//p3,p4都是int*类型

    int arr[5]={0};
    int (*p)[5]=&arr;
    parr pa=&arr; //pa等价于p

    int (*pf)(int,int)=&Add;
    padd pf2=&Add; //pf2等价于pf
    return 0; 
}
//那简化一下③中第二个代码：void (*signal(int,void(*)(int)))(int);
typedef void(*ps)(int);
ps signal(int,ps);//函数的声明
ps signal(int n,ps p)//函数的定义
{
    //……
    return p;
}
void test(int n)
{
    //……
}
int main()
{
    signal(10,&test);//函数的调用
    return 0;
}
//4️⃣函数指针数组
//存放函数指针的数组
//①理解：函数指针数组是指针数组的一种，数组中的元素都是同一类型的函数指针
int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int Mul(int x,int y)
{
    return x*y;
}
int Div(int x,int y)
{
    return x/y;
}
int main()
{
    int (*parr[4])(int,int)={Add,Sub,Mul,Div}; //初始化
    //parr就是函数指针数组名，[4]表示这个函数指针数组有4个元素，每个元素都是int(*)(int,int)这种函数指针类型
    int i,r;
    for(i=0;i<4;i++)
    {
        r=parr[i](9,3); //r=(*parr[i])(9,3);
        printf("%d ",r); 
    }
    return 0;
}//输出：12 6 27 3 
//②转移表（函数指针数组的用途）
//计算器的一般实现：
void menu()
{
    printf("1.add    2.sub\n");
    printf("3.mul    4.div\n");
    printf("    0.exit\n    ");
}
int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int Mul(int x,int y)
{
    return x*y;
}
int Div(int x,int y)
{
    return x/y;
}
int main()
{
    int input;
    int x,y;
    do
    {
        menu();
        printf("请选择：");
        scanf("%d",&input);
        switch(input)
        {
            case 1:
            printf("请输入操作数：");  //这里的代码冗余（重复）
            scanf("%d %d",&x,&y);
            printf("%d\n",Add(x,y));
            break;
            case 2:
            printf("请输入操作数：");
            scanf("%d %d",&x,&y);
            printf("%d\n",Sub(x,y));
            break;
            case 3:
            printf("请输入操作数：");
            scanf("%d %d",&x,&y);
            printf("%d\n",Mul(x,y));
            break;
            case 4:
            printf("请输入操作数：");
            scanf("%d %d",&x,&y);
            printf("%d\n",Div(x,y));
            break;
            case 0:
            printf("退出计算器\n");
            break;
            default:
            printf("选择错误，请重新选择\n");
            break;
        }
    } while(input);
    return 0;
}
//使用函数指针数组实现：
void menu()
{
    printf("1.add    2.sub\n");
    printf("3.mul    4.div\n");
    printf("    0.exit\n    ");
}
int Add(int x,int y)
{
    return x+y;
}
int Sub(int x,int y)
{
    return x-y;
}
int Mul(int x,int y)
{
    return x*y;
}
int Div(int x,int y)
{
    return x/y;
}
int main()
{
    int input;
    int x,y;
    int (*p[5])(int,int)={NULL,Add,Sub,Mul,Div}; //转移表
    //                     0    1   2   3   4
    do
    {
        menu();
        printf("请选择：");
        scanf("%d",&input);
        if(input==0)
        printf("退出计算器\n");
        else if(input>0&&input<=4)
        {
            printf("请输入操作数：");
            scanf("%d %d",&x,&y);
            printf("%d\n",p[input](x,y));
        }
        else
        printf("选择错误，请重新选择\n");
    } while(input);
    return 0;
}
//用回调函数实现：见8.3.c
